﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Document;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Input;
using System.Windows.Media.TextFormatting;
using System.Windows.Shapes;

namespace LogoScriptIDE
{
    /// <summary>
    /// 断点列，用于支持接受用户点击并添加断点及呈现断点
    /// </summary>
    public class BreakPointMargin : AbstractMargin
    {
        private TextArea m_textArea;
        private TextEditor m_editor;

        /// <summary>
        /// 构建函数
        /// </summary>
        /// <param name="editor">编辑器引用</param>
        public BreakPointMargin(TextEditor editor)
        {
            BreakPointBrush = Brushes.OrangeRed;
            PausingArrowBrush = Brushes.Yellow;
            m_editor = editor;
        }
        
        /// <inheritdoc/>
        protected override Size MeasureOverride(Size availableSize)
        {
            // 根据用户的字体大小来确认控件的大小
            if (TextView != null &&
                TextView.VisualLinesValid &&
                TextView.VisualLines.Count > 0)
            {
                return new Size(TextView.VisualLines[0].Height, 0);
            }
            else
            {
                return new Size(m_editor.FontSize + 3, 0);
            }
        }

        /// <summary>
        /// 获取和设置断点的画刷
        /// </summary>
        public Brush BreakPointBrush { get; set; }

        /// <summary>
        /// 获取和设置暂停箭头的画刷
        /// </summary>
        public Brush PausingArrowBrush { get; set; }

        /// <inheritdoc/>
        protected override void OnRender(DrawingContext drawingContext)
        {
            TextView textView = this.TextView;
            Size renderSize = this.RenderSize;

            if (textView != null && textView.VisualLinesValid)
            {
                // 计算断点的半径
                double radius = textView.VisualLines[0].Height / 2;

                // 遍历出现在界面中的每一行
                foreach (VisualLine line in textView.VisualLines)
                {
                    // 获取该行的行号和垂直方向的位置
                    int lineNumber = line.FirstDocumentLine.LineNumber;
                    double yOffset = line.VisualTop - textView.VerticalOffset;

                    // 判断该行是否存在断点，若存在则绘制断点
                    if (GetAnchorAtLine(lineNumber) != null)
                    {
                        drawingContext.DrawEllipse(BreakPointBrush, null,
                            new Point(radius, radius + yOffset),
                                radius * 0.9, radius * 0.9);
                    }

                    // 判断该行是否是暂停行，若是则绘制暂停箭头
                    if (lineNumber == PausingLineNumber)
                    {
                        Path myPath = new Path();
                        var geometry = new StreamGeometry();
                        double size = radius * 2;
                        
                        // 绘制三角形
                        using (StreamGeometryContext ctx = geometry.Open())
                        {
                            ctx.BeginFigure(new Point(size * 0.25, size * 0.15 + yOffset), true, true);
                            ctx.LineTo(new Point(size * 0.85, size * 0.5 + yOffset), true, false);
                            ctx.LineTo(new Point(size * 0.25, size * 0.85 + yOffset), true, false);
                        }
                        geometry.Freeze();
                        
                        drawingContext.DrawGeometry(
                            PausingArrowBrush, null, geometry);
                    }
                }
            }
        }

        // 代码来自AvalonEdit的示例程序
        /// <inheritdoc/>
        protected override void OnTextViewChanged(TextView oldTextView, TextView newTextView)
        {
            if (oldTextView != null)
            {
                oldTextView.VisualLinesChanged -= TextViewVisualLinesChanged;
            }
            base.OnTextViewChanged(oldTextView, newTextView);
            if (newTextView != null)
            {
                newTextView.VisualLinesChanged += TextViewVisualLinesChanged;
                // find the text area belonging to the new text view
                m_textArea = newTextView.Services.GetService(typeof(TextArea)) as TextArea;
            }
            else
            {
                m_textArea = null;
            }
            InvalidateVisual();
        }

        /// <summary>
        /// 显示行发生变化的回调函数
        /// </summary>
        void TextViewVisualLinesChanged(object sender, EventArgs e)
        {
            RefreshAnchors();
            InvalidateVisual();
        }

        /// <inheritdoc/>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            // 鼠标左键回调函数，添加或删除断点
            if (!e.Handled && TextView != null && m_textArea != null)
            {
                e.Handled = true;
                m_textArea.Focus();

                // 先获取被点击位置所在的行
                Point pos = e.GetPosition(TextView);
                pos.X = 0;
                pos.Y += TextView.VerticalOffset;
                VisualLine vl = TextView.GetVisualLineFromVisualTop(pos.Y);
                if (vl == null) return;
                int lineNumber = vl.FirstDocumentLine.LineNumber;
                RefreshAnchors();

                // 判断该行是否已经有断点
                TextAnchor anchor = GetAnchorAtLine(lineNumber);
                if (anchor == null)
                {
                    // 该行不存在断点，则向该行添加断点
                    int refer = lineNumber;
                    var newAnchor = CreateAnchorAtLine(ref refer, false);
                    if (newAnchor != null)
                        m_anchors.Add(newAnchor);
                }
                else // 该行已存在断点，则删除改行的断点
                    m_anchors.Remove(anchor);

                // 刷新界面
                InvalidateVisual();
            }
        }

        // 代码来自AvalonEdit的示例程序
        /// <inheritdoc/>
        protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
        {
            // accept clicks even when clicking on the background
            return new PointHitTestResult(this, hitTestParameters.HitPoint);
        }

        // 保存所有断点
        private ICollection<TextAnchor> m_anchors = new HashSet<TextAnchor>();

        /// <summary>
        /// 刷新所有断点
        /// </summary>
        private void RefreshAnchors()
        {
            HashSet<TextAnchor> newAnchors = new HashSet<TextAnchor>();
            HashSet<int> lines = new HashSet<int>();
            foreach (var anchor in m_anchors)
            {
                // 如果对应断点位置被删除，或这一行已存在另一个断点，则该断点被删除
                if (anchor.IsDeleted)
                    continue;
                if (lines.Contains(anchor.Line))
                    continue;

                // 重新在该断点所在行的行首处生成一个断点
                int lineNumber = anchor.Line;
                var newAnchor = CreateAnchorAtLine(ref lineNumber, false);
                if (newAnchor != null)
                {
                    lines.Add(lineNumber);
                    newAnchors.Add(newAnchor);
                }
            }
            m_anchors = newAnchors;
        }

        /// <summary>
        /// 获取某一行的断点位置
        /// </summary>
        /// <param name="lineNumber">行号</param>
        /// <returns>对应的断点位置，若不存在则返回null</returns>
        private TextAnchor GetAnchorAtLine(int lineNumber)
        {
            foreach (var anchor in m_anchors)
            {
                if (anchor.Line == lineNumber) return anchor;
            }
            return null;
        }

        /// <summary>
        /// 在某一行处添加断点
        /// </summary>
        /// <param name="lineNumber">
        /// 行号，插入完成后返回断点所在行的行号
        /// 由于可能插入失败后延至下一行，因此返回值与传入值可能不一致
        /// </param>
        /// <param name="strict">
        /// 对于该行无法插入断点的情况，若strict为真则放弃插入，否则尝试向改行的下一行插入
        /// </param>
        /// <returns>插入的断点位置，若失败则返回null</returns>
        private TextAnchor CreateAnchorAtLine(ref int lineNumber, bool strict)
        {
            // 找到待插入断点的行
            var line = Document.GetLineByNumber(lineNumber);
            int offset = line.Offset;

            // 找到该行的第一个有效字符，插入断点
            while (offset < line.EndOffset)
            {
                // 若该行的第一个有效字符是注释，则表示改行无效，插入失败
                if (Document.Text[offset] == '`') break;
                if (Char.IsWhiteSpace(Document.Text[offset]))
                    ++offset;
                else // 该行的第一个有效字符不是注释，则向该位置插入断点并返回
                    return Document.CreateAnchor(offset);
            }

            // 如果插入失败，并且不允许向下一行插入，则取消插入
            if (strict || lineNumber == Document.LineCount)
                return null;
            else
            {
                // 尝试着向下一行处插入断点
                lineNumber++;
                return CreateAnchorAtLine(ref lineNumber, strict);
            }
        }

        /// <summary>
        /// 设置断点
        /// 对于试图在不能设置断点的行处设置断点
        /// 那么程序会尝试向这一行的下面第一行可以插入断点的行插入断点
        /// </summary>
        /// <param name="lineNumbers">断点对应的行号集合</param>
        public void SetBreakPoints(ICollection<int> lineNumbers)
        {
            // 先清空断点
            ClearBreakPoints();
            foreach (int number in lineNumbers)
            {
                int refer = number;
                var anchor = CreateAnchorAtLine(ref refer, true);
                if (anchor == null)
                    throw new Exception("Invalid line number: " + number.ToString());
                m_anchors.Add(anchor);
            }
        }

        /// <summary>
        /// 获得当前所有断点
        /// </summary>
        /// <param name="lineNumbers">当前所有被标记为断点的行的行号的集合</param>
        public void GetBreakPoints(ICollection<int> lineNumbers)
        {
            lineNumbers.Clear();
            foreach (var anchor in m_anchors)
                lineNumbers.Add(anchor.Line);
        }

        /// <summary>
        /// 清空所有断点
        /// </summary>
        public void ClearBreakPoints()
        {
            m_anchors.Clear();
        }

        private int m_pausingLineNumber;
        /// <summary>
        /// 获取或设置暂停行的行号
        /// </summary>
        public int PausingLineNumber
        {
            get { return m_pausingLineNumber; }
            set { m_pausingLineNumber = value; }
        }
    }
}
